package handler

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/apache/rocketmq-client-go/v2"
	"github.com/apache/rocketmq-client-go/v2/consumer"
	"github.com/apache/rocketmq-client-go/v2/primitive"
	"github.com/apache/rocketmq-client-go/v2/producer"
	"github.com/opentracing/opentracing-go"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"math/rand"
	"mxshop_srvs/order_srv/global"
	"mxshop_srvs/order_srv/model"
	"mxshop_srvs/order_srv/proto"
	"time"
)

type OrderServer struct {
	proto.UnimplementedOrderServer
}

func GenerateOrderSn(userId int32) string {
	//订单号的生成规则
	/*
		年月日时分秒+用户id+2位随机数
	*/
	now := time.Now()
	rand.Seed(time.Now().UnixNano())
	orderSn := fmt.Sprintf("%d%d%d%d%d%d%d%d",
		now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Nanosecond(),
		userId, rand.Intn(90)+10,
	)
	return orderSn
}

/*
*
添加购物车
*/
func (c *OrderServer) CreateCartItem(ctx context.Context, req *proto.CartItemRequest) (*proto.ShopCartInfoResponse, error) {
	var cartItem model.ShoppingCart
	if result := global.DB.Where(&model.ShoppingCart{User: req.UserId, Goods: req.GoodsId}).First(&cartItem); result.RowsAffected == 0 {
		//新增
		cartItem.User = req.UserId
		cartItem.Goods = req.GoodsId
		cartItem.Nums = req.Nums
		cartItem.Checked = false
	} else {
		//修改
		cartItem.Nums += req.Nums
	}
	global.DB.Save(&cartItem)
	return &proto.ShopCartInfoResponse{Id: cartItem.ID}, nil
}

/*
*
更新购物车记录，更新选中状态和数量
*/
func (c *OrderServer) UpdateCartItem(ctx context.Context, req *proto.CartItemRequest) (*emptypb.Empty, error) {
	var cartItem model.ShoppingCart
	if result := global.DB.Where(&model.ShoppingCart{User: req.UserId, Goods: req.GoodsId}).First(&cartItem); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "当前用户的该商品的购物车记录不存在")
	}
	cartItem.Checked = req.Checked
	if req.Nums > 0 {
		cartItem.Nums = req.Nums
	}
	global.DB.Save(&cartItem)
	return &emptypb.Empty{}, nil
}

func (c *OrderServer) DeleteCartItem(ctx context.Context, req *proto.CartItemRequest) (*emptypb.Empty, error) {
	var cartItem model.ShoppingCart
	if result := global.DB.Where(&model.ShoppingCart{User: req.UserId, Goods: req.GoodsId}).First(&cartItem); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "购物车记录不存在")
	}
	global.DB.Delete(&cartItem)
	return &emptypb.Empty{}, nil
}

/*
*
购物车列表
*/
func (c *OrderServer) CartItemList(ctx context.Context, req *proto.UserInfo) (*proto.CartItemListResponse, error) {
	parentSpan := opentracing.SpanFromContext(ctx)
	var cartItemListResponse proto.CartItemListResponse
	var cartItems []model.ShoppingCart
	var shopCartInfoResponse []*proto.ShopCartInfoResponse
	cartItemListSpan := opentracing.GlobalTracer().StartSpan("cartItemList", opentracing.ChildOf(parentSpan.Context()))
	if result := global.DB.Where(&model.ShoppingCart{User: req.Id}).Find(&cartItems); result.Error != nil {
		return nil, status.Errorf(codes.NotFound, "购物车记录列表不存在")
	} else {
		cartItemListResponse.Total = int32(result.RowsAffected)
		for _, item := range cartItems {
			shopCartInfoResponse = append(shopCartInfoResponse, &proto.ShopCartInfoResponse{
				Id:      item.ID,
				UserId:  item.User,
				GoodsId: item.Goods,
				Nums:    item.Nums,
				Checked: false,
			})
		}
		cartItemListResponse.Data = shopCartInfoResponse
	}
	cartItemListSpan.Finish()
	return &cartItemListResponse, nil
}

/*
*
获取订单列表
*/
func (c *OrderServer) OrderList(ctx context.Context, req *proto.OrderFilterRequest) (*proto.OrderListResponse, error) {
	var orderInfoList []model.OrderInfo
	var total int64
	var orderListResponse proto.OrderListResponse
	var data []*proto.OrderInfoResponse
	global.DB.Model(&model.OrderInfo{}).Where(&model.OrderInfo{User: req.UserId}).Count(&total)
	orderListResponse.Total = int32(total)
	global.DB.Scopes(Paginate(int(req.Pages), int(req.PagePerNums))).Where(&model.OrderInfo{User: req.UserId}).Find(&orderInfoList)
	for _, orderInfo := range orderInfoList {
		data = append(data, &proto.OrderInfoResponse{
			Id:      orderInfo.ID,
			UserId:  req.UserId,
			OrderSn: orderInfo.OrderSn,
			PayType: orderInfo.PayType,
			Status:  orderInfo.Status,
			Post:    orderInfo.Post,
			Total:   orderInfo.OrderMount,
			Address: orderInfo.Address,
			Name:    orderInfo.SignerName,
			Mobile:  orderInfo.SingerMobile,
			AddTime: orderInfo.CreatedAt.Format("2006-01-02 15:04:05"),
		})
	}
	orderListResponse.Data = data
	return &orderListResponse, nil
}

/*
*
获取订单详情
*/
func (c *OrderServer) OrderDetail(ctx context.Context, req *proto.OrderRequest) (*proto.OrderInfoDetailResponse, error) {
	var orderInfo model.OrderInfo
	var orderGoods []model.OrderGoods
	var orderInfoDetail proto.OrderInfoDetailResponse
	var orderInfoResponse *proto.OrderInfoResponse
	var orderItemResponse []*proto.OrderItemResponse
	//查询订单详情
	if result := global.DB.Where(&model.OrderInfo{BaseModel: model.BaseModel{ID: req.Id}, User: req.UserId}).First(&orderInfo); result.Error != nil {
		return nil, result.Error
	}
	orderInfoResponse = &proto.OrderInfoResponse{
		Id:      orderInfo.ID,
		UserId:  orderInfo.User,
		OrderSn: orderInfo.OrderSn,
		PayType: orderInfo.PayType,
		Status:  orderInfo.Status,
		Post:    orderInfo.Post,
		Total:   orderInfo.OrderMount,
		Address: orderInfo.Address,
		Name:    orderInfo.SignerName,
		Mobile:  orderInfo.SingerMobile,
		AddTime: orderInfo.CreatedAt.Format("2006-12-12"),
	}
	orderInfoDetail.OrderInfo = orderInfoResponse
	//查询订单下的商品
	if result := global.DB.Where(&model.OrderGoods{Order: orderInfo.ID}).Find(&orderGoods); result.RowsAffected == 0 {
		return nil, result.Error
	}
	for _, good := range orderGoods {
		orderItemResponse = append(orderItemResponse, &proto.OrderItemResponse{
			Id:         good.ID,
			OrderId:    good.Order,
			GoodsId:    good.Goods,
			GoodsName:  good.GoodsName,
			GoodsImage: good.GoodsImage,
			GoodsPrice: good.GoodsPrice,
			Nums:       good.Nums,
		})
	}
	orderInfoDetail.Goods = orderItemResponse
	return &orderInfoDetail, nil
}

/*
*
创建订单
*/
/*func (c *OrderServer) CreateOrder(ctx context.Context, req *proto.OrderRequest) (*proto.OrderInfoResponse, error) {
	orderSn := GenerateOrderSn(req.UserId) //生成订单号
	//1 判断购物车中是否有商品
	var goodsIds []int32
	var shoppingCart []model.ShoppingCart
	goodsNumsMap := make(map[int32]int32)
	if result := global.DB.Where(&model.ShoppingCart{User: req.UserId, Checked: true}).Find(&shoppingCart); result.RowsAffected == 0 {
		zap.S().Errorf("当前用户购物成中没有商品")
		return nil, status.Errorf(codes.Internal, "当前用户购物成中没有商品:%d", req.UserId)
	}
	for _, cart := range shoppingCart {
		goodsIds = append(goodsIds, cart.Goods)
		goodsNumsMap[cart.Goods] = cart.Nums
	}
	//2 调用商品服务批量查询商品信息
	for _, cart := range shoppingCart {
		goodsIds = append(goodsIds, cart.Goods)
	}
	//查询商品列表
	goodsList, err := global.GoodsSrvClient.BatchGetGoods(context.Background(), &proto.BatchGoodsIdInfo{Id: goodsIds})
	if err != nil {
		zap.S().Errorf("批量查询商品失败")
		return nil, status.Errorf(codes.Internal, "批量查询商品失败")
	}
	//3 调用库存服务扣减库存
	var sellInfo proto.SellInfo
	var goodsInvInfo []*proto.GoodsInvInfo
	var orderMount float32
	var orderGoodsList []*model.OrderGoods
	for _, goods := range goodsList.Data {
		orderMount += goods.ShopPrice * float32(goodsNumsMap[goods.Id]) //获取总价
		goodsInvInfo = append(goodsInvInfo, &proto.GoodsInvInfo{
			GoodsId: goods.Id,
			Num:     goodsNumsMap[goods.Id],
		})
		orderGoodsList = append(orderGoodsList, &model.OrderGoods{
			Goods:      goods.Id,
			GoodsName:  goods.Name,
			GoodsImage: goods.GoodsFrontImage,
			GoodsPrice: goods.ShopPrice,
			Nums:       goodsNumsMap[goods.Id],
		})
	}
	sellInfo.GoodsInfo = goodsInvInfo
	sellInfo.OrderSn = orderSn
	_, err = global.InventorySrvClient.Sell(context.Background(), &sellInfo)
	if err != nil {
		zap.S().Errorf("扣减库存服务失败:", err)
		return nil, status.Errorf(codes.Internal, "扣减库存服务失败")
	}
	//4 插入订单数据
	var orderInfo model.OrderInfo
	orderInfo.OrderSn = orderSn
	orderInfo.Address = req.Address
	orderInfo.SignerName = req.Name
	orderInfo.Post = req.Post
	orderInfo.User = req.UserId
	orderInfo.OrderMount = orderMount
	orderInfo.Status = "PAYING" //
	tx := global.DB.Begin()
	if result := tx.Save(&orderInfo); result.RowsAffected == 0 {
		zap.S().Errorf("创建订单失败")
		tx.Rollback()
		return nil, status.Errorf(codes.Internal, "创建订单失败")
	}
	//5 插入订单商品(orderGoods)
	for _, orderGoods := range orderGoodsList {
		orderGoods.Order = orderInfo.ID
	}
	if batches := tx.CreateInBatches(orderGoodsList, 100); batches.RowsAffected == 0 {
		zap.S().Errorf("创建订单商品失败")
		tx.Rollback()
		return nil, status.Errorf(codes.Internal, "创建订单商品失败")
	}
	//6 删除购物车的商品
	if delRes := tx.Where(&model.ShoppingCart{User: orderInfo.User, Checked: true}).Delete(&model.ShoppingCart{}); delRes.RowsAffected == 0 {
		zap.S().Errorf("删除购物车的商品失败")
		tx.Rollback()
		return nil, status.Errorf(codes.Internal, "删除购物车的商品失败")
	}
	//7 发送延时消息(当订单超时未支付的时候使用)
	fmt.Println("发送订单超时消息...")
	tx.Commit()
	return &proto.OrderInfoResponse{
		Id:      orderInfo.ID,
		UserId:  req.UserId,
		OrderSn: orderInfo.OrderSn,
		AddTime: orderInfo.CreatedAt.Format("2006-01-01"),
	}, nil
}
*/

type OrderListener struct {
	Code        codes.Code
	Detail      string
	ID          int32
	OrderAmount float32
	Ctx         context.Context
}

/*
*
利用rocketmq的可靠消息的最终一致性，解决事务问题
*/
func (c *OrderServer) CreateOrder(ctx context.Context, req *proto.OrderRequest) (*proto.OrderInfoResponse, error) {
	orderListener := OrderListener{
		Ctx: ctx,
	}
	p, err := rocketmq.NewTransactionProducer(
		&orderListener,
		producer.WithNameServer([]string{"192.168.200.161:9876"}),
		producer.WithGroupName("mxshop-order-transaction"),
		producer.WithInstanceName("mxshop-order-transaction-"+GenerateOrderSn(1000)),
	)
	if err != nil {
		zap.S().Errorf("生成producer失败:%s\n", err)
		return nil, err
	}
	if err = p.Start(); err != nil {
		zap.S().Errorf("启动producer失败:%s\n", err)
		return nil, err
	}
	var orderInfo model.OrderInfo
	orderInfo.OrderSn = GenerateOrderSn(req.UserId)
	orderInfo.Address = req.Address
	orderInfo.SignerName = req.Name
	orderInfo.Post = req.Post
	orderInfo.User = req.UserId
	orderInfo.Status = "PAYING" //
	msg, _ := json.Marshal(orderInfo)
	res, err := p.SendMessageInTransaction(context.Background(), primitive.NewMessage("order_reback", msg))
	if err != nil {
		zap.S().Errorf("发送order_reback消息失败:%s\n", err)
	} else {
		zap.S().Infof("发送order_reback消息成功:%s\n", res.String())
	}
	//执行完本地事务后获取orderListener中的code
	if orderListener.Code != codes.OK {
		return nil, status.Error(orderListener.Code, orderListener.Detail)
	}
	return &proto.OrderInfoResponse{Id: orderListener.ID, OrderSn: orderInfo.OrderSn, Total: orderListener.OrderAmount}, nil
}

/*
*
执行本地事务
*/
func (o *OrderListener) ExecuteLocalTransaction(msg *primitive.Message) primitive.LocalTransactionState {
	parentSpan := opentracing.SpanFromContext(o.Ctx)
	var orderInfo model.OrderInfo
	_ = json.Unmarshal(msg.Body, &orderInfo)
	zap.S().Infof("本地事务处理订单消息:%v", orderInfo)
	zap.S().Infof("本地事务处理订单监控消息:%v", o)
	//1 判断购物车中是否有商品
	var goodsIds []int32
	var shoppingCart []model.ShoppingCart
	goodsNumsMap := make(map[int32]int32)
	selectShopCartspan := opentracing.GlobalTracer().StartSpan("select_shop_cart", opentracing.ChildOf(parentSpan.Context()))
	if result := global.DB.Where(&model.ShoppingCart{User: orderInfo.User, Checked: true}).Find(&shoppingCart); result.RowsAffected == 0 {
		zap.S().Errorf("当前用户购物成中没有商品")
		o.Code = codes.Internal
		o.Detail = "当前用户购物成中没有商品"
		return primitive.RollbackMessageState
	}
	selectShopCartspan.Finish()
	for _, cart := range shoppingCart {
		goodsIds = append(goodsIds, cart.Goods)
		goodsNumsMap[cart.Goods] = cart.Nums
	}
	//2 调用商品服务批量查询商品信息
	for _, cart := range shoppingCart {
		goodsIds = append(goodsIds, cart.Goods)
	}
	selectGoodsSpan := opentracing.GlobalTracer().StartSpan("select_goods", opentracing.ChildOf(parentSpan.Context()))
	//查询商品列表
	goodsList, err := global.GoodsSrvClient.BatchGetGoods(context.Background(), &proto.BatchGoodsIdInfo{Id: goodsIds})
	if err != nil {
		zap.S().Errorf("批量查询商品失败")
		o.Code = codes.Internal
		o.Detail = "批量查询商品失败"
		return primitive.RollbackMessageState
	}
	selectGoodsSpan.Finish()
	//3 调用库存服务扣减库存
	var sellInfo proto.SellInfo
	var goodsInvInfo []*proto.GoodsInvInfo
	var orderMount float32
	var orderGoodsList []*model.OrderGoods
	for _, goods := range goodsList.Data {
		orderMount += goods.ShopPrice * float32(goodsNumsMap[goods.Id]) //获取总价
		goodsInvInfo = append(goodsInvInfo, &proto.GoodsInvInfo{
			GoodsId: goods.Id,
			Num:     goodsNumsMap[goods.Id],
		})
		orderGoodsList = append(orderGoodsList, &model.OrderGoods{
			Goods:      goods.Id,
			GoodsName:  goods.Name,
			GoodsImage: goods.GoodsFrontImage,
			GoodsPrice: goods.ShopPrice,
			Nums:       goodsNumsMap[goods.Id],
		})
	}
	sellInfo.GoodsInfo = goodsInvInfo
	sellInfo.OrderSn = orderInfo.OrderSn
	selectGoodsSpan.Finish()
	inventorySellSpan := opentracing.GlobalTracer().StartSpan("inventory_sell", opentracing.ChildOf(parentSpan.Context()))
	_, err = global.InventorySrvClient.Sell(context.Background(), &sellInfo)
	if err != nil {
		zap.S().Errorf("扣减库存服务失败:%v\n", err)
		o.Code = codes.Internal
		o.Detail = "扣减库存服务失败"
		return primitive.RollbackMessageState
	}
	inventorySellSpan.Finish()
	//4 插入订单数据
	orderInfo.OrderMount = orderMount
	orderInfo.Status = "PAYING" //
	tx := global.DB.Begin()
	insertOrderSpan := opentracing.GlobalTracer().StartSpan("insert_order", opentracing.ChildOf(parentSpan.Context()))
	if result := tx.Save(&orderInfo); result.RowsAffected == 0 {
		zap.S().Errorf("创建订单失败")
		o.Code = codes.Internal
		o.Detail = "创建订单失败"
		tx.Rollback()
		return primitive.CommitMessageState
	}
	insertOrderSpan.Finish()
	//5 插入订单商品(orderGoods)
	for _, orderGoods := range orderGoodsList {
		orderGoods.Order = orderInfo.ID
	}
	insertOrderGoodsSpan := opentracing.GlobalTracer().StartSpan("insert_order_goods", opentracing.ChildOf(parentSpan.Context()))
	if batches := tx.CreateInBatches(orderGoodsList, 100); batches.RowsAffected == 0 {
		zap.S().Errorf("插入订单商品失败")
		o.Code = codes.Internal
		o.Detail = "插入订单商品失败"
		tx.Rollback()
		return primitive.CommitMessageState
	}
	insertOrderGoodsSpan.Finish()
	//6 删除购物车的商品
	deleteShopCartSpan := opentracing.GlobalTracer().StartSpan("delete_shop_cart", opentracing.ChildOf(parentSpan.Context()))
	if delRes := tx.Where(&model.ShoppingCart{User: orderInfo.User, Checked: true}).Delete(&model.ShoppingCart{}); delRes.RowsAffected == 0 {
		zap.S().Errorf("删除购物车的商品失败")
		o.Code = codes.Internal
		o.Detail = "删除购物车的商品失败"
		tx.Rollback()
		return primitive.CommitMessageState
	}
	deleteShopCartSpan.Finish()
	//7 发送延时消息(当订单超时未支付的时候使用)
	p, err := rocketmq.NewProducer(
		producer.WithNameServer([]string{"192.168.200.161:9876"}),
		producer.WithGroupName("mxshop-order-timout"),
		producer.WithInstanceName("mxshop-order-timout-"+GenerateOrderSn(1000)), //实例唯一防止重复
	)
	if err != nil {
		//panic("生成producer失败")
		zap.S().Errorf("生成producer失败:%s\n", err)
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = "生成producer失败"
		return primitive.CommitMessageState
	}
	if err = p.Start(); err != nil {
		//panic("启动producer失败")
		zap.S().Errorf("启动producer失败:%s\n", err)
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = "启动producer失败"
		return primitive.CommitMessageState
	}
	//1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
	message := primitive.NewMessage("order_timeout", msg.Body)
	message.WithDelayTimeLevel(5)
	res, err := p.SendSync(context.Background(), message)
	if err != nil {
		zap.S().Errorf("延时消息发送失败:%s\n", err)
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = "延时消息发送失败"
		return primitive.CommitMessageState
	} else {
		zap.S().Infof("延时消息发送成功:%s\n", res.String())
	}
	//if err = p.Shutdown(); err != nil {
	//	//panic("关闭producer失败")
	//	zap.S().Errorf("关闭producer失败:%s\n", err)
	//	tx.Rollback()
	//	o.Code = codes.Internal
	//	o.Detail = "关闭producer失败"
	//	return primitive.CommitMessageState
	//}
	tx.Commit()
	o.Code = codes.OK
	o.Detail = "本地事务执行成功"
	return primitive.RollbackMessageState
}

/*
*
本地事务回查
*/
func (o *OrderListener) CheckLocalTransaction(msg *primitive.MessageExt) primitive.LocalTransactionState {
	zap.S().Info("本地消息回查")
	var orderInfo model.OrderInfo
	_ = json.Unmarshal(msg.Body, &orderInfo)
	if result := global.DB.Where(&model.OrderInfo{OrderSn: orderInfo.OrderSn}).Find(&orderInfo); result.RowsAffected == 0 {
		zap.S().Errorf("本地消息回查失败:%v\n", result.Error)
		return primitive.CommitMessageState
	}
	return primitive.RollbackMessageState
}

/*
*
订单超时消费者的回调方法
*/
func OrderTimeOut(ctx context.Context, msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
	for _, msg := range msgs {
		//1 解析消息
		var orderInfo model.OrderInfo
		_ = json.Unmarshal(msg.Body, &orderInfo)
		zap.S().Infof("获取到订单超时消息: %v\n", &orderInfo)
		//2 判断订单是否成功，否则发送归还库存的消息
		if result := global.DB.Where(&model.OrderInfo{User: orderInfo.User, OrderSn: orderInfo.OrderSn}).First(&orderInfo); result.RowsAffected == 0 {
			return consumer.ConsumeSuccess, nil
		}
		if orderInfo.Status != "TRADE_SUCCESS" {
			orderInfo.Status = "TRADE_CLOSED"
			tx := global.DB.Begin()
			tx.Save(&orderInfo)
			//发送订单超时的消息给库存服务
			p, err := rocketmq.NewProducer(
				producer.WithNameServer([]string{"192.168.200.161:9876"}),
				producer.WithGroupName("mxshop-reback-inventory"),
				producer.WithInstanceName("mxshop-reback-inventory-"+GenerateOrderSn(1000)),
			)
			if err != nil {
				zap.S().Error("新建producer失败")
				return consumer.ConsumeRetryLater, nil
			}
			if err = p.Start(); err != nil {
				zap.S().Error("启动producer失败")
				return consumer.ConsumeRetryLater, nil
			}
			_, err = p.SendSync(context.Background(), primitive.NewMessage("order_reback", msg.Body))
			if err != nil {
				tx.Rollback()
				zap.S().Error("订单超时消息发送失败: %s\n", err)
				return consumer.ConsumeRetryLater, nil
			}
			/*if err = p.Shutdown(); err != nil {
				//panic("关闭producer失败")
				zap.S().Error("关闭producer失败")
				return consumer.ConsumeRetryLater, nil
			}*/
			tx.Commit()
		}
		return consumer.ConsumeSuccess, nil
	}
	return consumer.ConsumeSuccess, nil
}
